#include "spl.includes.h"
#include "splib.h"

#define BUTTON_ROUND        3

#define CTRL_TEXTOUT        0x66FFFFFF
#define CTRL_TEXT           0xFF333333

#define CTRL_BORDERLIGHT    0x88FFFFFF
#define CTRL_BORDERDARK     0x88000000

#define FADE_INTERVAL       25
#define FADE_ID             0x0100
#define FADE_FRAMES         5

using namespace SPLib;
using namespace SPLib::Controls;
using namespace SPLib::Geom;
using namespace Gdip;

VOID    Button::onTimer(Event::TimerEvent& iEvent)
{
  if (iEvent.id == FADE_ID)
  {
    BOOL animate = FALSE;

    if (frameHighlight != 0 && frameHighlight != FADE_FRAMES)
    {
      frameHighlight += fHovered ? 1 : -1;
      animate = TRUE;
    }

    if (frameShadow != 0 && frameShadow != FADE_FRAMES)
    {
      frameShadow += fClicked ? 1 : -1;
      animate = TRUE;
    }

    if (animate)
      invalidate(FALSE);
    else
      removeTimer(iEvent.id);
  }
}

VOID    Button::doClick()
{
  NMHDR data;

  data.hwndFrom   = *this;
  data.idFrom     = getID();
  data.code       = NM_CLICK;

  SendMessage(getParent(), WM_NOTIFY, (WPARAM)getID(), (LPARAM)&data);
}

VOID    Button::onKeyDown(Event::KeyEvent& iEvent)
{
  // if enter or space pressed, send clicked event to parent
  switch (iEvent.vkCode)
  {
  case VK_SPACE:
  case VK_RETURN:
    doClick();
    break;
  }
}

VOID    Button::onSetFocus(Event::FocusEvent& iEvent)
{
  // button got focus, set focus flag and redraw
  fFocused = TRUE;
  invalidate();
}

VOID    Button::onKillFocus(Event::FocusEvent& iEvent)
{
  // button lost focus, reset focus flag and redraw
  fFocused = FALSE;
  invalidate();
}

VOID    Button::onMouseMove(Event::MouseEvent& iEvent)
{
  // if button was not hovered
  if (!fHovered)
  {
    // set hovered flag
    fHovered = TRUE;

    // start fade timer
    ++frameHighlight;
    setTimer(FADE_ID, FADE_INTERVAL);

    // invalidate control region
    invalidate();
  }
}

VOID    Button::onMouseLeave()
{
  // reset clicked flag
  fClicked = FALSE;
  // reset hovered flag
  fHovered = FALSE;

  // start fade timer
  --frameHighlight;
  setTimer(FADE_ID, FADE_INTERVAL);

  // invalidate control region
  invalidate();
}

VOID    Button::onMouseDown(Event::MouseEvent& iEvent)
{
  // if button is hovered but was not clicked
  if (!fClicked)
  {
    // set clicked flag
    fClicked = TRUE;

    // start fade timer
    ++frameShadow;
    setTimer(FADE_ID, FADE_INTERVAL);

    // select button
    focus();
    // invalidate control region
    invalidate();
  }
}

VOID    Button::onMouseUp(Event::MouseEvent& iEvent)
{
  // if button is clicked
  if (fClicked)
  {
    // reset clicked flag
    fClicked = FALSE;

    // start fade timer
    --frameShadow;
    setTimer(FADE_ID, FADE_INTERVAL);

    // send clicked message if mouse over button
    if (Geom::Rect::fromClient(*this).contains(iEvent.point))
      doClick();

    // invalidate control region
    invalidate();
  }
}

VOID    Button::onControlPaint(Controls::ControlPaintEvent& iEvent)
{
  // paint button background
  paintBackground(iEvent.graphics, iEvent.bounds);
  // paint button text
  paintText(iEvent.graphics, iEvent.bounds);
}

VOID    Button::paintBackground(
  Graphics &g,
  const Gdip::Rect &bounds
  )
{
  // button shape, inset shape
  GraphicsPath path, inpath;
  // button border pen
  Pen border(CTRL_BORDERLIGHT);
  // button bounds
  Gdip::Rect btnBounds(
    bounds.X,
    bounds.Y,
    bounds.Width - 1,
    bounds.Height - 1);

  // construct button paths
  getRoundedRect(path, btnBounds, BUTTON_ROUND);
  btnBounds.Inflate(-1, -1);
  getRoundedRect(inpath, btnBounds, BUTTON_ROUND);

  // if enabled
  if (isEnabled())
  {
    // paint foreground color
    g.FillPath(&Gdip::LinearGradientBrush(btnBounds,
      ((0xCF * frameHighlight / FADE_FRAMES) * 0x01000000) +
      (foreColor & 0x00FFFFFF),
      ((0xFF * frameHighlight / FADE_FRAMES) * 0x01000000) +
      (foreColor & 0x00FFFFFF),
      LinearGradientModeVertical), &path);

    // paint lightness
    g.FillPath(&Gdip::LinearGradientBrush(btnBounds,
      ((0x11 * (FADE_FRAMES - frameHighlight) / FADE_FRAMES + 0x22) *
      0x01000000) + 0x00FFFFFF,
      ((0x11 * (FADE_FRAMES - frameHighlight) / FADE_FRAMES + 0x22) *
      0x01000000) + 0x00000000,
      LinearGradientModeVertical), &path);

    // paint thick inner border if control has focus
    if (fFocused)
    {
      // thick border path
      GraphicsPath thick;

      // get thick path
      btnBounds.Inflate(-2, -2);
      getRoundedRect(thick, btnBounds, BUTTON_ROUND - 2);
      btnBounds.Inflate(2, 2);

      // set pen color and size
      border.SetColor(0x3F000000);
      border.SetWidth(3.0f);

      // draw thick border
      g.DrawPath(&border, &thick);
    }

    // paint light reflexion
    {
      btnBounds.Height = btnBounds.Height / 2;

      g.SetClip(btnBounds);
      g.FillPath(&Gdip::LinearGradientBrush(btnBounds,
        0x7FFFFFFF, 0x3FFFFFFF, LinearGradientModeVertical), &inpath);
      g.ResetClip();

      btnBounds.Height = btnBounds.Height * 2 + 1;
    }

    // paint shadow
    g.FillPath(&Gdip::LinearGradientBrush(btnBounds,
      ((0x5F * frameShadow / FADE_FRAMES +
      0x44 * frameHighlight / FADE_FRAMES) * 0x01000000) + 0x00000000,
      ((0x00 * frameShadow / FADE_FRAMES) * 0x01000000) + 0x00000000,
      LinearGradientModeVertical), &path);
  }

  // paint inner border and border
  {
    border.SetWidth(fClicked ? 2.0f : 1.0f);
    border.SetColor(CTRL_BORDERDARK);
    g.DrawPath(&border, fClicked ? &inpath : &path);

    if (!fClicked)
    {
      border.SetWidth(1.0f);
      border.SetColor(CTRL_BORDERLIGHT);
      g.DrawPath(&border, &inpath);
    }
  }
}

VOID    Button::paintText(
  Graphics &g,
  const Gdip::Rect &bounds
  )
{
  // text shape
  GraphicsPath text;
  // button text
  TCHAR szText[MAX_PATH + 1];
  // button text brush
  SolidBrush brush(CTRL_TEXT);
  // button text outline pen
  Pen outline(CTRL_TEXTOUT, 3.0f);
  // button text format
  StringFormat format;
  // text bounds
  Gdip::Rect txtBounds = bounds;

  // lower text if pressed
  if (fClicked) txtBounds.Y += 1;

  // get button text
  getText(szText, MAX_PATH + 1);

  // set text alignment and trimming
  format.SetAlignment(StringAlignmentCenter);
  format.SetLineAlignment(StringAlignmentCenter);
  format.SetTrimming(StringTrimmingCharacter);
  format.SetFormatFlags(StringFormatFlagsNoClip);

  // construct text path
  g.SetSmoothingMode(SmoothingModeNone);
  text.AddString(szText, -1, &FontFamily(_T("Arial")),
    FontStyleBold, 13, txtBounds, &format);
  g.SetSmoothingMode(SmoothingModeAntiAlias);

  // paint button outline and text
  g.DrawPath(&outline, &text);
  g.FillPath(&brush, &text);
}
